#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import tempfile
import re
import logging
import uuid
from typing import Dict, List, Any, BinaryIO, Tuple, Optional, Union
from pathlib import Path
import shutil
from datetime import datetime

import fitz  # PyMuPDF
from PIL import Image
import io

from document_processor.engines.base_engine import BaseEngine
from image_handler.obs_uploader import OBSUploader

logger = logging.getLogger(__name__)

class PDFMinerEngine(BaseEngine):
    """PDF处理引擎，负责PDF文档转换为Markdown以及图片提取处理
    
    基于PyMuPDF(fitz)工具处理PDF文档，实现文本提取、图片处理和Markdown转换
    """
    
    def __init__(self, config: Dict[str, Any]):
        """初始化PDF处理引擎
        
        Args:
            config: 配置字典
        """
        super().__init__(config)
        self.supported_image_formats = {'jpg', 'png', 'jp2', 'jpeg', 'jfif'}
        # 初始化OBS上传器
        self.obs_uploader = OBSUploader(config) if "obs" in config else None
        logger.info("已初始化PDF处理引擎")
    
    async def convert_to_markdown(
        self, 
        file_content: BinaryIO, 
        file_name: str,
        image_option: str = "obs",
        work_dir: Optional[str] = None,
        md_filename: Optional[str] = None
    ) -> Dict[str, Any]:
        """将PDF文档转换为Markdown格式
        
        Args:
            file_content: 文件内容（二进制流）
            file_name: 文件名
            image_option: 图片处理选项，可选值："obs"（上传到OBS）或"remove"（移除图片）
            work_dir: 工作目录路径，如果提供则使用此目录，否则创建临时目录
            md_filename: 生成的Markdown文件名（不含扩展名），用于命名图片目录
            
        Returns:
            包含以下键的字典:
                - text: 提取的纯文本
                - markdown: 转换后的Markdown文本
                - images: 图片信息列表
                - temp_dir: 临时目录路径
        """
        # 检查文件格式
        file_ext = os.path.splitext(file_name)[1].lower()
        if file_ext != '.pdf':
            raise ValueError(f"PDF引擎只支持PDF格式，不支持: {file_ext}")
            
        # 使用提供的工作目录或创建临时目录
        temp_dir = work_dir if work_dir else tempfile.mkdtemp()
        try:
            # 确保目录存在，使用绝对路径
            temp_dir = os.path.abspath(temp_dir)
            if not os.path.exists(temp_dir):
                os.makedirs(temp_dir, exist_ok=True)
                logger.info(f"创建临时目录: {temp_dir}")
            
            # 确保output目录存在
            output_dir = os.path.join(temp_dir, "output")
            output_dir = os.path.abspath(output_dir)
            if not os.path.exists(output_dir):
                os.makedirs(output_dir, exist_ok=True)
                logger.info(f"创建输出目录: {output_dir}")
            
            # 生成MD文件名
            base_name = Path(file_name).stem
            md_filename_local = md_filename or base_name
            
            # 创建图片输出目录，使用Markdown文件名作为目录名
            images_dir = None
            
            if image_option != "obs":
                # 使用Markdown文件名作为目录名
                images_dir_name = f"{md_filename_local}_images"
                images_dir = os.path.join(output_dir, images_dir_name)
                images_dir = os.path.abspath(images_dir)
                if not os.path.exists(images_dir):
                    os.makedirs(images_dir, exist_ok=True)
                    logger.info(f"创建图片目录: {images_dir}")
            
            # 确保所有目录都存在
            for dir_path in [temp_dir, output_dir]:
                if not os.path.exists(dir_path):
                    os.makedirs(dir_path, exist_ok=True)
                    logger.info(f"确保目录存在: {dir_path}")
            
            if images_dir and not os.path.exists(images_dir):
                os.makedirs(images_dir, exist_ok=True)
                logger.info(f"确保图片目录存在: {images_dir}")
            
            # 生成输出文件路径
            output_file = os.path.join(output_dir, f"{md_filename_local}.md")
            output_file = os.path.abspath(output_file)
            
            # 确保输出文件的父目录存在
            output_parent_dir = os.path.dirname(output_file)
            if not os.path.exists(output_parent_dir):
                os.makedirs(output_parent_dir, exist_ok=True)
                logger.info(f"创建输出文件父目录: {output_parent_dir}")
            
            temp_file_path = os.path.join(temp_dir, file_name)
            temp_file_path = os.path.abspath(temp_file_path)
            
            try:
                # 保存上传的文件到临时位置
                file_content_bytes = file_content.read()
                with open(temp_file_path, "wb") as f:
                    f.write(file_content_bytes)
                
                logger.info(f"开始处理PDF文档: {file_name}")
                
                # 提取文本和图片位置
                text, image_placeholders = self._extract_text(temp_file_path)
                markdown_text = self._convert_to_markdown(text)
                logger.info(f"文本提取和转换完成，包含 {len(image_placeholders)} 个图片占位符")
                
                # 清理元数据标记和水印内容
                markdown_text = self._clean_markdown_text(markdown_text)
                logger.info("已清理Markdown文本中的元数据标记和水印内容")
                
                # 初始化图片列表
                images = []
                
                # 根据图片处理选项处理图片
                if image_option == "remove":
                    # 移除所有图片占位符
                    logger.info("图片处理选项为'remove'，移除所有图片占位符")
                    markdown_text = self._remove_image_placeholders(markdown_text)
                else:
                    # 提取图片
                    try:
                        logger.info("开始提取PDF图片")
                        images = self._extract_images(temp_file_path)
                        logger.info(f"成功提取 {len(images)} 张图片")
                        
                        # 使用OBS上传器处理图片
                        if self.obs_uploader:
                            logger.info("使用OBS上传器处理图片")
                            markdown_text = await self.handle_images(images, markdown_text, self.obs_uploader)
                        else:
                            logger.warning("未配置OBS上传器，无法处理图片")
                            markdown_text = self._remove_image_placeholders(markdown_text)
                    except Exception as e:
                        logger.error(f"提取图片失败: {str(e)}")
                        # 图片提取失败不应该影响整个转换过程
                        images = []
                
                # 再次确保输出目录存在
                output_dir = os.path.dirname(output_file)
                if not os.path.exists(output_dir):
                    os.makedirs(output_dir, exist_ok=True)
                    logger.info(f"确保输出目录存在: {output_dir}")
                
                # 保存Markdown内容到临时文件
                with open(output_file, 'w', encoding='utf-8') as f:
                    f.write(markdown_text)
                
                return {
                    "text": re.sub(r'\[图片_\d+_\d+\]', '', markdown_text),  # 纯文本（移除图片引用）
                    "markdown": markdown_text,
                    "images": images,
                    "temp_dir": temp_dir,
                    "images_dir": images_dir,  # 添加图片目录信息
                    "output_md": output_file   # 添加输出文件路径
                }
                
            except Exception as e:
                logger.error(f"处理文件出错: {str(e)}")
                import traceback
                logger.error(f"详细错误: {traceback.format_exc()}")
                # 不再自动清理临时目录，以便用户可以访问生成的图片
                # if not work_dir and temp_dir and os.path.exists(temp_dir):
                #     shutil.rmtree(temp_dir, ignore_errors=True)
                raise
        except Exception as e:
            logger.error(f"创建临时目录失败: {str(e)}")
            raise

    def _extract_text(self, file_path: str) -> Tuple[str, List[Dict[str, Any]]]:
        """提取PDF文本内容和图片占位符
        
        Args:
            file_path: PDF文件路径
            
        Returns:
            Tuple[str, List]: 提取的文本内容和图片占位符信息列表
        """
        if not Path(file_path).exists():
            raise FileNotFoundError(f"PDF文件不存在: {file_path}")
            
        text_content = []
        image_placeholders = []
        
        try:
            doc = fitz.open(file_path)
            
            # 先预扫描所有图片，确定每页有多少张图片和它们的位置
            page_images_info = {}
            
            for page_num, page in enumerate(doc, 1):
                # 获取实际可用图片数量和位置
                image_info_list = page.get_image_info()
                valid_images = []
                
                for img_info in image_info_list:
                    if not self._should_skip_image(page, img_info):
                        # 对于有效图片，记录边界框位置用于后续插入占位符
                        bbox = img_info.get("bbox")
                        if bbox:
                            valid_images.append({
                                "bbox": bbox,
                                # 添加图片垂直位置用于排序
                                "y_pos": bbox[1]  # y0坐标
                            })
                
                if valid_images:
                    # 按垂直位置排序图片
                    valid_images.sort(key=lambda x: x["y_pos"])
                    page_images_info[page_num] = valid_images
                    logger.info(f"第 {page_num} 页有 {len(valid_images)} 张有效图片")
            
            # 处理每一页的文本和图片占位符
            for page_num, page in enumerate(doc, 1):
                # 添加页码标记
                page_marker = f"## 第 {page_num} 页\n"
                text_content.append(page_marker)
                
                # 获取该页的图片信息
                page_images = page_images_info.get(page_num, [])
                
                if not page_images:
                    # 如果没有图片，直接添加文本
                    page_text = page.get_text()
                    if page_text:
                        text_content.append(page_text.strip())
                    continue
                
                # 获取页面的文本块
                blocks = page.get_text("blocks")
                
                if not blocks:
                    # 如果没有文本块，但有图片，直接添加图片占位符
                    for idx, img_info in enumerate(page_images, 1):
                        placeholder = f"[图片_{page_num}_{idx}]"
                        text_content.append(placeholder)
                        image_placeholders.append({
                            "page": page_num,
                            "index": idx,
                            "placeholder": placeholder
                        })
                    continue
                
                # 按照垂直位置排序文本块
                blocks.sort(key=lambda b: b[1])  # 按y0排序
                
                # 用于跟踪已处理的图片
                processed_images = 0
                
                # 处理每个文本块，在合适的位置插入图片
                for i, block in enumerate(blocks):
                    # 获取文本块的垂直位置
                    block_y = block[1]  # y0
                    block_text = block[4]
                    
                    # 检查是否有图片应该在这个文本块之前插入
                    while (processed_images < len(page_images) and 
                           page_images[processed_images]["y_pos"] <= block_y):
                        # 在当前文本块前插入图片占位符
                        img_idx = processed_images + 1
                        placeholder = f"[图片_{page_num}_{img_idx}]"
                        text_content.append(placeholder)
                        image_placeholders.append({
                            "page": page_num,
                            "index": img_idx,
                            "placeholder": placeholder
                        })
                        processed_images += 1
                    
                    # 添加文本块
                    if block_text.strip():
                        text_content.append(block_text.strip())
                
                # 添加剩余未处理的图片占位符
                while processed_images < len(page_images):
                    img_idx = processed_images + 1
                    placeholder = f"[图片_{page_num}_{img_idx}]"
                    text_content.append(placeholder)
                    image_placeholders.append({
                        "page": page_num,
                        "index": img_idx,
                        "placeholder": placeholder
                    })
                    processed_images += 1
                    
            doc.close()
            return "\n\n".join(text_content), image_placeholders
        except Exception as e:
            logger.error(f"提取PDF文本时出错: {str(e)}")
            import traceback
            logger.error(f"详细错误: {traceback.format_exc()}")
            raise

    def _get_page_images_info(self, page, page_num: int) -> List[Dict[str, Any]]:
        """获取页面中的图片信息
        
        Args:
            page: fitz页面对象
            page_num: 页码
            
        Returns:
            List[Dict]: 页面中图片信息列表
        """
        # 获取页面中的图片
        valid_images = []
        
        try:
            # 使用get_image_info而不是get_images获取当前页的图片
            image_info_list = page.get_image_info()
            
            if not image_info_list:
                return []
                
            # 过滤和处理图片信息
            for img_index, img_info in enumerate(image_info_list):
                # 使用number字段作为图片标识符
                img_number = img_info.get("number")
                if img_number is None:
                    continue
                    
                # 跳过太小的图片和可能的水印
                if self._should_skip_image(page, img_info):
                    continue
                    
                valid_images.append({
                    "number": img_number,  # 使用number而不是xref
                    "index": img_index + 1,
                    "page": page_num,
                    "bbox": img_info.get("bbox")
                })
                
            logger.info(f"第 {page_num} 页找到 {len(valid_images)} 张有效图片")
            return valid_images
            
        except Exception as e:
            logger.error(f"获取第 {page_num} 页图片信息失败: {str(e)}")
            return []

    def _should_skip_image(self, page, img_info) -> bool:
        """判断是否应该跳过某个图片
        
        Args:
            page: 页面对象
            img_info: 图片信息
            
        Returns:
            bool: 是否应该跳过
        """
        try:
            # 获取图片边界框
            bbox = img_info.get("bbox")
            if not bbox:
                logger.debug("跳过无边界框的图片")
                return True
            
            x0, y0, x1, y1 = bbox
            width = x1 - x0
            height = y1 - y0
            
            # 获取页面大小
            page_width = page.rect.width
            page_height = page.rect.height
            
            # 基本尺寸过滤 - 跳过太小的图片
            if width < 50 or height < 50:
                logger.debug(f"跳过小尺寸图片: {width}x{height}")
                return True
            
            # 计算图片面积和页面面积
            image_area = width * height
            page_area = page_width * page_height
            
            # 过滤太大的区域，可能是整页截图而非单一图片
            # 但如果图片尺寸合理且比例正常，可能是一个合法的大图，不应该跳过
            if image_area / page_area > 0.8:
                logger.debug(f"跳过过大区域: {width}x{height}, 占比: {image_area/page_area:.2f}")
                return True
            
            # 宽高比过滤，但降低敏感度，避免过滤掉细长的图表
            aspect_ratio = width / height if height > 0 else 0
            if aspect_ratio > 10 or aspect_ratio < 0.1:
                logger.debug(f"跳过宽高比例异常的区域: {width}x{height}, 比例: {aspect_ratio:.2f}")
                return True
            
            # 过滤页眉页脚区域可能的水印或页码
            is_header = y0 < page_height * 0.05
            is_footer = y1 > page_height * 0.95
            
            # 只过滤页眉页脚区域的小图片，允许页眉页脚区域的大图
            if (is_header or is_footer) and (width < 150 and height < 50):
                logger.debug(f"跳过页眉页脚小区域: {width}x{height}")
                return True
            
            # 页眉页脚区域的细长图像可能是页码或页眉线
            if (is_header or is_footer) and (aspect_ratio > 8 or aspect_ratio < 0.2):
                logger.debug(f"跳过页眉页脚的细长区域: 比例={aspect_ratio:.2f}")
                return True
            
            # 检查区域内是否包含文本，如果包含大量文本可能是混合区域而非纯图片
            # 但是需要注意一些图表可能包含少量文本标签
            try:
                text_in_area = page.get_text("text", clip=bbox)
                # 如果区域内有超过一定数量的文本，则可能不是纯图片
                # 但要考虑图表可能包含标签文本的情况
                if len(text_in_area.strip()) > 100:
                    logger.debug(f"跳过含大量文本的区域, 文本长度: {len(text_in_area.strip())}")
                    return True
                
                # 只在大面积区域应用文本密度检测
                if image_area > 10000:  # 只对较大区域检查文本密度
                    # 计算文本密度，但降低敏感度
                    text_density = len(text_in_area.strip()) / image_area
                    if text_density > 0.02:  # 提高文本密度阈值
                        logger.debug(f"跳过文本密度高的区域: 密度={text_density:.4f}")
                        return True
            except Exception:
                pass  # 忽略文本提取错误
            
            # 通过所有筛选条件，保留这个图片
            return False
        
        except Exception as e:
            logger.warning(f"检查图片是否应跳过时出错: {str(e)}")
            return False  # 出错时不跳过，保留图片

    def _convert_to_markdown(self, text: str) -> str:
        """将提取的文本转换为Markdown格式
        
        Args:
            text: 从PDF提取的原始文本
            
        Returns:
            str: 转换后的Markdown文本
        """
        # 处理标题和段落
        markdown_lines = []
        
        # 使用空行分割段落
        paragraphs = text.split("\n\n")
        
        for paragraph in paragraphs:
            paragraph = paragraph.strip()
            if not paragraph:
                continue
            
            # 清理图片元数据标记，如 <image: DeviceRGB, width: 1700, height: 1700, bpc: 8>
            if re.match(r'<image:.+>', paragraph):
                continue
                
            # 处理章节标题
            if paragraph.startswith("## 第"):
                markdown_lines.append(paragraph)
                continue
                
            # 处理图片占位符
            if re.match(r'\[图片_\d+_\d+\]', paragraph):
                markdown_lines.append(paragraph)
                continue
                
            # 检测是否是标题
            if len(paragraph) < 100 and not paragraph.endswith("."):
                if paragraph.isupper() or paragraph.istitle():
                    # 可能是一级标题
                    markdown_lines.append(f"# {paragraph}\n")
                    continue
                    
            # 处理列表项
            if paragraph.startswith("• ") or paragraph.startswith("* "):
                paragraph = paragraph.replace("• ", "* ")
                markdown_lines.append(paragraph)
                continue
                
            # 清理段落中的图片元数据标记
            paragraph = re.sub(r'<image:.+?>', '', paragraph)
            
            # 普通段落
            markdown_lines.append(paragraph)
            
            # 确保段落之间有空行
            markdown_lines.append("")
            
        return "\n".join(markdown_lines)
    
    def _remove_image_placeholders(self, markdown_text: str) -> str:
        """移除所有图片占位符
        
        Args:
            markdown_text: Markdown内容
            
        Returns:
            str: 移除图片占位符后的内容
        """
        return re.sub(r'\[图片_\d+_\d+\]', '', markdown_text)

    def _clean_markdown_text(self, markdown_text: str) -> str:
        """清理Markdown文本中的元数据标记和水印相关内容
        
        Args:
            markdown_text: 原始Markdown内容
            
        Returns:
            str: 清理后的Markdown内容
        """
        # 清理图片元数据标记 - 精确匹配 <image: DeviceRGB, width: xxxx, height: xxxx, bpc: 8> 格式
        cleaned_text = re.sub(r'<image:\s*[\w]+,\s*width:\s*\d+,\s*height:\s*\d+,\s*bpc:\s*\d+>', '', markdown_text)
        
        # 清理更宽泛的图片元数据标记 - 以防格式有变化
        cleaned_text = re.sub(r'<image:.+?>', '', cleaned_text)
        
        # 清理可能的水印文本标记 - 通常和图片关联的水印文本
        cleaned_text = re.sub(r'watermark\s*\d*', '', cleaned_text, flags=re.IGNORECASE)
        
        # 清理多余的空行 - 连续超过2个的空行替换为2个
        cleaned_text = re.sub(r'\n{3,}', '\n\n', cleaned_text)
        
        # 清理任何单独行上的图片元数据信息
        lines = cleaned_text.split('\n')
        filtered_lines = []
        
        for line in lines:
            # 跳过包含图片元数据的行
            if re.search(r'image:.+width:.+height:', line.strip()):
                continue
            filtered_lines.append(line)
        
        cleaned_text = '\n'.join(filtered_lines)
        
        return cleaned_text

    def _extract_images(self, file_path: str) -> List[Dict[str, Any]]:
        """提取PDF中的图片
        
        Args:
            file_path: PDF文件路径
            
        Returns:
            List[Dict]: 图片信息列表
        """
        if not Path(file_path).exists():
            raise FileNotFoundError(f"PDF文件不存在: {file_path}")
            
        images = []
        
        try:
            doc = fitz.open(file_path)
            
            # 获取文档中的所有页面
            total_pages = len(doc)
            logger.info(f"PDF文档共有 {total_pages} 页")
            
            # 先统计总的图片数量
            total_images = 0
            for page_num in range(1, total_pages + 1):
                try:
                    page = doc.load_page(page_num - 1)
                    image_info_list = page.get_image_info()
                    if image_info_list:
                        total_images += len(image_info_list)
                except Exception:
                    continue
            
            logger.info(f"PDF文档中包含 {total_images} 张图片")
            
            #########################################################
            # 水印检测功能 - 仅当图片数量大于等于15张时才进行检测
            #########################################################
            
            # 初始化水印尺寸为None
            possible_watermark_size = None
            
            # 只有当图片数量大于等于15张时才进行水印检测
            if total_images >= 15:
                logger.info("图片数量大于等于15张，开始进行水印检测")
                # 检测水印图片：先收集前10页中下标0位置图片的尺寸
                watermark_sizes = {}
                max_sample_pages = min(10, total_pages)
                
                for page_num in range(1, max_sample_pages + 1):
                    try:
                        page = doc.load_page(page_num - 1)
                        image_info_list = page.get_image_info()
                        
                        if not image_info_list:
                            continue
                            
                        # 检查下标0位置的图片
                        if len(image_info_list) > 0:
                            first_img = image_info_list[0]
                            bbox = first_img.get("bbox")
                            
                            if bbox:
                                # 计算尺寸作为标识
                                x0, y0, x1, y1 = bbox
                                width = x1 - x0
                                height = y1 - y0
                                size_key = f"{width:.1f}x{height:.1f}"
                                
                                if size_key not in watermark_sizes:
                                    watermark_sizes[size_key] = 1
                                else:
                                    watermark_sizes[size_key] += 1
                    except Exception:
                        continue
                
                # 确定可能的水印尺寸
                watermark_threshold = max_sample_pages * 0.7  # 70%的页面有相同尺寸，认为是水印
                
                for size_key, count in watermark_sizes.items():
                    if count >= watermark_threshold:
                        possible_watermark_size = size_key
                        logger.info(f"【水印检测】成功检测到水印图片，尺寸为 {size_key}，在前{max_sample_pages}页中出现了{count}次")
                        break
            else:
                logger.info("图片数量少于15张，跳过水印检测")
            
            # 遍历每一页提取图片
            for page_num, page in enumerate(doc, 1):
                try:
                    # 获取页面图片信息
                    image_info_list = page.get_image_info()
                    
                    if not image_info_list:
                        logger.info(f"第 {page_num} 页未找到图片")
                        continue
                    
                    # 图片索引计数器
                    image_index = 1
                    
                    # 处理每个图片
                    for img_idx, img_info in enumerate(image_info_list):
                        try:
                            # 水印过滤：如果检测到水印，并且是第一个图片，检查尺寸是否匹配
                            if possible_watermark_size and img_idx == 0:
                                bbox = img_info.get("bbox")
                                if bbox:
                                    x0, y0, x1, y1 = bbox
                                    width = x1 - x0
                                    height = y1 - y0
                                    current_size = f"{width:.1f}x{height:.1f}"
                                    
                                    # 如果尺寸匹配水印尺寸，跳过此图片
                                    if current_size == possible_watermark_size:
                                        logger.debug(f"【水印过滤】跳过水印图片，位于第 {page_num} 页，下标 {img_idx}")
                                        continue
                        
                            # 跳过不符合要求的图片
                            if self._should_skip_image(page, img_info):
                                continue
                                
                            # 获取图片边界框
                            bbox = img_info.get("bbox")
                            if not bbox:
                                continue
                                
                            # 使用高分辨率获取图片
                            zoom_factor = 2.0
                            clip_rect = fitz.Rect(bbox)
                            
                            try:
                                # 使用矩阵提高分辨率
                                mat = fitz.Matrix(zoom_factor, zoom_factor)
                                pix = page.get_pixmap(matrix=mat, clip=clip_rect, alpha=False)
                            except Exception as e:
                                logger.warning(f"使用高分辨率提取图片失败: {str(e)}，使用默认分辨率")
                                # 使用默认分辨率
                                pix = page.get_pixmap(clip=bbox)
                                
                            if not pix:
                                continue
                                
                            # 转换为PNG格式
                            img_data = pix.tobytes("png")
                            
                            # 获取图片尺寸
                            width = pix.width
                            height = pix.height
                            
                            # 跳过太小的图片
                            if width < 100 or height < 100:
                                continue
                                
                            # 记录图片信息
                            images.append({
                                "data": img_data,
                                "format": "png",
                                "page": page_num,
                                "index": image_index,
                                "placeholder": f"[图片_{page_num}_{image_index}]",
                                "bbox": bbox,
                                "width": width,
                                "height": height
                            })
                            
                            logger.info(f"成功提取图片: page={page_num}, index={image_index}, 大小={width}x{height}")
                            
                            # 增加索引计数
                            image_index += 1
                            
                        except Exception as e:
                            logger.warning(f"处理第 {page_num} 页图片失败: {str(e)}")
                            continue
                            
                except Exception as e:
                    logger.error(f"处理第 {page_num} 页时出错: {str(e)}")
                    continue
                
            # 按页码和索引排序
            images.sort(key=lambda img: (img["page"], img["index"]))
            
            doc.close()
            logger.info(f"总共提取 {len(images)} 张有效图片")
            return images
            
        except Exception as e:
            logger.error(f"提取PDF图片时出错: {str(e)}")
            import traceback
            logger.error(f"详细错误: {traceback.format_exc()}")
            
            # 确保文档已关闭
            try:
                if 'doc' in locals() and doc:
                    doc.close()
            except:
                pass
            
            raise

    def _extract_image_data(self, doc, xref) -> Optional[Dict[str, Any]]:
        """提取图片数据
        
        Args:
            doc: PDF文档对象
            xref: 图片引用ID
            
        Returns:
            Optional[Dict]: 包含图片数据的字典，提取失败返回None
        """
        try:
            base_image = doc.extract_image(xref)
            
            if not base_image or "image" not in base_image or "ext" not in base_image:
                return None
            
            # 确保扩展名是支持的格式
            ext = base_image["ext"].lower()
            if ext not in self.supported_image_formats:
                if ext == "jb2":  # 特殊处理某些格式
                    ext = "jpg"
                else:
                    ext = "png"  # 默认使用PNG格式
            
            return {
                "image": base_image["image"],
                "ext": ext
            }
            
        except Exception as e:
            logger.warning(f"提取图片数据失败: xref={xref}, 错误: {str(e)}")
            return None

    def _save_images_to_files(self, images: List[Dict[str, Any]], output_dir: str, dir_name: str = "images") -> List[Dict[str, Any]]:
        """将图片保存到文件
        
        Args:
            images: 图片信息列表
            output_dir: 输出目录路径（这个目录不会被自动清理）
            dir_name: 图片目录名称，用于构建相对路径
            
        Returns:
            List[Dict]: 更新后的图片信息列表（添加了文件路径）
        """
        saved_images = []
        
        for i, image in enumerate(images):
            try:
                page_num = image.get("page", 1)
                img_index = image.get("index", i+1)
                
                # 生成更有意义的文件名，包含页码和索引信息，避免中文
                img_filename = f"page{page_num}_{img_index:03d}.{image['format']}"
                img_path = os.path.join(output_dir, img_filename)
                
                # 保存图片到文件
                with open(img_path, "wb") as img_file:
                    img_file.write(image["data"])
                
                # 获取图片尺寸
                width = image.get("width", 0)
                height = image.get("height", 0)
                
                # 如果没有尺寸信息，尝试从图片文件获取
                if width == 0 or height == 0:
                    try:
                        pil_img = Image.open(io.BytesIO(image["data"]))
                        width, height = pil_img.size
                    except Exception as e:
                        logger.warning(f"获取图片尺寸失败: {str(e)}")
                
                # 更新图片信息
                placeholder_id = f"IMAGE_{page_num}_{img_index}"
                
                saved_image = {
                    "path": img_path,
                    "filename": img_filename,
                    "format": image["format"],
                    "page": page_num,
                    "index": img_index,
                    "width": width,
                    "height": height,
                    "placeholder_id": placeholder_id,
                    "dir_name": dir_name  # 添加目录名称用于构建相对路径
                }
                
                saved_images.append(saved_image)
                logger.info(f"已保存图片: {img_filename}, 大小: {width}x{height}")
                
            except Exception as e:
                logger.error(f"保存图片到文件失败: {str(e)}")
                import traceback
                logger.debug(f"详细错误: {traceback.format_exc()}")
                continue
                
        return saved_images
    
    async def handle_images(self, images: List[Dict[str, Any]], markdown: str, obs_uploader: Optional[Any] = None) -> str:
        """处理Markdown中的图片，上传到OBS
        
        Args:
            images: 图片信息列表
            markdown: Markdown内容
            obs_uploader: OBS上传器实例
            
        Returns:
            str: 更新后的Markdown内容
        """
        if not images:
            logger.info("没有图片需要处理")
            # 即使没有图片也进行一次清理
            return self._clean_markdown_text(markdown)
        
        # 如果没有提供OBS上传器，则无法处理图片
        if not obs_uploader:
            logger.warning("未提供OBS上传器，无法处理图片")
            return self._clean_markdown_text(markdown)
        
        # 按页码和索引排序图片
        images.sort(key=lambda img: (img.get("page", 1), img.get("index", 999)))
        logger.info(f"开始处理 {len(images)} 张图片")
        
        # 生成唯一的时间戳和标识符
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        unique_id = str(uuid.uuid4())[:8]
        
        # 首先检查Markdown中是否已经包含图片链接格式，如果有，可能是之前已经处理过
        if re.search(r'!\[图片_\d+_\d+\]\(.+?\)', markdown):
            logger.warning("检测到Markdown中已包含图片链接格式，将跳过处理避免重复")
            return markdown
            
        # 解析所有占位符
        placeholder_pattern = r'\[图片_(\d+)_(\d+)\]'
        all_placeholders = re.findall(placeholder_pattern, markdown)
        logger.info(f"在Markdown中找到 {len(all_placeholders)} 个图片占位符")
        
        # 为每张图片获取URL（上传到OBS）
        image_urls = {}
        
        for image in images:
            page_num = image.get("page", 1)
            img_idx = image.get("index", 1)
            placeholder = f"[图片_{page_num}_{img_idx}]"
            
            try:
                # 准备上传到OBS的数据
                upload_data = {}
                
                # 检查图片是否有二进制数据
                if "data" in image and image["data"]:
                    # 直接使用内存中的图片数据
                    upload_data["data"] = image["data"]
                    upload_data["format"] = image.get("format", "png")
                    # 创建更有意义的文件名，包含时间戳和唯一标识符
                    upload_data["filename"] = f"{timestamp}_{unique_id}/page{page_num}_{img_idx}.{image.get('format', 'png')}"
                    logger.info(f"使用内存数据直接上传图片: {upload_data['filename']}")
                elif "path" in image and os.path.exists(image["path"]):
                    # 使用已保存的图片文件
                    upload_data["path"] = image["path"]
                    logger.info(f"使用本地文件上传图片: {image['path']}")
                else:
                    logger.warning(f"图片数据不完整，无法上传: {placeholder}")
                    continue
                
                # 上传到OBS
                image_url = await obs_uploader.upload(upload_data)
                if not image_url:
                    logger.warning(f"上传图片失败: {placeholder}")
                    continue
                logger.info(f"已上传图片到OBS: {placeholder} -> {image_url}")
                
                # 使用实际占位符文本作为键
                image_urls[placeholder] = image_url
                logger.info(f"处理图片: {placeholder} -> {image_url}")
                
            except Exception as e:
                logger.error(f"处理图片失败: {str(e)}")
                import traceback
                logger.debug(f"详细错误: {traceback.format_exc()}")
                continue
        
        # 一次性替换所有占位符，避免多次字符串替换
        processed_markdown = markdown
        
        # 创建正则表达式替换模式
        def replace_placeholder(match):
            """替换占位符为Markdown图片链接"""
            placeholder = match.group(0)
            if placeholder not in image_urls:
                return placeholder  # 未找到对应的URL，保留原占位符
            
            url = image_urls[placeholder]
            
            # 获取图片尺寸信息
            page_num, img_idx = match.groups()
            img_caption = f"图片_{page_num}_{img_idx}"
            
            # 不添加尺寸限制，避免影响图片预览
            return f"![{img_caption}]({url})"
        
        # 使用正则替换
        processed_markdown = re.sub(placeholder_pattern, replace_placeholder, processed_markdown)
        
        # 检查是否存在错误的双重图片链接格式: !![图片_x_y](url)(url)
        double_link_pattern = r'!\[!\[图片_(\d+)_(\d+)\]\((.+?)\)\]\((.+?)\)'
        if re.search(double_link_pattern, processed_markdown):
            logger.warning("检测到双重图片链接格式，进行修复")
            # 修复双重链接格式为正常的单链接格式
            processed_markdown = re.sub(double_link_pattern, r'![图片_\1_\2](\3)', processed_markdown)
        
        # 检查是否还有未替换的占位符
        remaining_placeholders = re.findall(placeholder_pattern, processed_markdown)
        if remaining_placeholders:
            logger.warning(f"还有 {len(remaining_placeholders)} 个未替换的占位符")
            
            # 只清理独立成行的未替换占位符，保留内嵌在文本中的占位符（可能有语义）
            lines = processed_markdown.split('\n')
            filtered_lines = []
            
            for line in lines:
                # 如果一行只包含占位符，则跳过此行
                if re.match(r'^\s*\[图片_\d+_\d+\]\s*$', line):
                    logger.debug(f"移除独立占位符行: {line.strip()}")
                    continue
                filtered_lines.append(line)
            
            processed_markdown = '\n'.join(filtered_lines)
        else:
            logger.info("所有占位符都已替换")
        
        # 清理元数据标记和水印内容
        processed_markdown = self._clean_markdown_text(processed_markdown)
        logger.info("图片处理完成，并清理了元数据标记和水印内容")
        
        return processed_markdown
        
    async def extract_images(
        self, 
        file_content: BinaryIO,
        file_name: str,
        page_images_info: Optional[Dict[int, List[Dict[str, Any]]]] = None,
        image_option: str = "obs",
        md_filename: Optional[str] = None
    ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
        """从PDF文档中提取图片
        
        Args:
            file_content: 文件内容（二进制流）
            file_name: 文件名
            page_images_info: 页面图片位置信息字典（此参数不再使用，但保留以兼容接口）
            image_option: 图片处理选项，仅支持："obs"（上传到OBS）
            md_filename: 生成的Markdown文件名（不含扩展名），用于命名图片目录
            
        Returns:
            Tuple[图片信息列表, 临时目录路径]
        """
        # 验证图片处理选项
        if image_option != "obs":
            logger.warning(f"不支持的图片处理选项: {image_option}，使用 obs 选项")
            image_option = "obs"
        
        temp_dir = tempfile.mkdtemp()
        temp_file_path = os.path.join(temp_dir, file_name)
        
        try:
            # 保存上传的文件到临时位置
            file_content_bytes = file_content.read()
            with open(temp_file_path, "wb") as f:
                f.write(file_content_bytes)
            
            logger.info(f"开始从PDF提取图片: {file_name}")
            
            # 使用简化的方法提取图片
            images = self._extract_images(temp_file_path)
            logger.info(f"从PDF文档中提取了 {len(images)} 张图片")
            
            # 直接使用提取的图片数据
            logger.info("使用OBS模式，将直接处理提取的图片")
            
            return images, temp_dir
                
        except Exception as e:
            logger.error(f"提取图片失败: {str(e)}")
            import traceback
            logger.error(f"详细错误: {traceback.format_exc()}")
            raise 